/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.ast;

import java.util.ArrayList;
import java.util.List;

import edu.ucsd.db.bassarisk.exception.VisitorException;
import edu.ucsd.db.bassarisk.query.Constants;
import edu.ucsd.db.bassarisk.query.ValidationTraversalLog;
import edu.ucsd.db.bassarisk.query.ast.SyntaxExpressions.NonterminalSyntaxExpression;
import edu.ucsd.db.bassarisk.query.ast.SyntaxExpressions.SyntaxExpression;
import edu.ucsd.db.bassarisk.query.ast.SyntaxExpressions.TerminalSyntaxExpression;
import edu.ucsd.db.bassarisk.query.expression.Expressions.BooleanAnd;
import edu.ucsd.db.bassarisk.query.expression.Expressions.BooleanNot;
import edu.ucsd.db.bassarisk.query.expression.Expressions.BooleanOr;
import edu.ucsd.db.bassarisk.query.expression.Expressions.Expression;
import edu.ucsd.db.bassarisk.query.expression.Expressions.KeywordMatchExpr;
import edu.ucsd.db.bassarisk.query.expression.Expressions.PositionMaxSeparation;
import edu.ucsd.db.bassarisk.query.expression.Expressions.PositionMinSeparation;
import edu.ucsd.db.bassarisk.query.expression.Expressions.PositionOrdered;
import edu.ucsd.db.bassarisk.query.expression.Expressions.PositionPhrase;
import edu.ucsd.db.bassarisk.query.expression.Expressions.PositionSeparation;
import edu.ucsd.db.bassarisk.query.expression.Expressions.PositionWindow;
import edu.ucsd.db.bassarisk.query.expression.Expressions.StructureFind;
import edu.ucsd.db.bassarisk.query.expression.Expressions.StructureIn;
import edu.ucsd.db.bassarisk.query.expression.Expressions.StructureSame;
import edu.ucsd.db.bassarisk.schema.RunClass;

public class PredicateResolutionTransformation implements
    SyntaxExpressionVisitor<Expression, ValidationTraversalLog> {
  private void requireAtMostOneInput(final List<Expression> resolvedSubExprs,
                                     final NonterminalSyntaxExpression expr,
                                     final ValidationTraversalLog arg) {
    if (expr.getArguments().size() > 1) {
      String msg = "Expression is only allowed one sub-expression.  Found %s.";
      arg.addError(expr, String.format(msg, expr.getArguments().size()));
    }
  }

  @Override
  public Expression
      visitNonterminalAbstractExpression(final NonterminalSyntaxExpression expr,
                                         final ValidationTraversalLog args)
          throws VisitorException {
    // Resolve sub-expressions
    List<Expression> resolvedSubExprs = new ArrayList<Expression>();
    for (SyntaxExpression subExpr : expr.getSubExpressions()) {
      Expression input = subExpr.accept(this, args);
      if (input != null) {
        resolvedSubExprs.add(subExpr.accept(this, args));
      }
    }
    // Check input cardinalities
    this.warnAtLeastOneInput(resolvedSubExprs, expr, args);
    switch (expr.getName()) {
    case "NOT":
    case "IN":
      this.requireAtMostOneInput(resolvedSubExprs, expr, args);
      break;
    default:
      // do nothing
    }
    // Check argument cardinalities and parse arguments
    String runClassName = Constants.PositionSpaceFlag;
    int sizeParam = -1;
    switch (expr.getName()) {
    // Five expressions have no arguments
    case "AND":
    case "OR":
    case "NOT":
    case "ORDERED":
    case "PHRASE":
      this.warnExcessArguments(expr, args, 0);
      break;
    // Three expressions have one argument (run-class name)
    case "FIND":
    case "IN":
    case "SAME":
      this.warnExcessArguments(expr, args, 1);
      if (expr.getArguments().size() < 1) {
        String msg = "Required argument (run-class name) missing.";
        args.addError(expr, msg);
      } else {
        runClassName = expr.getArguments().get(0);
      }
      break;
    case "WINDOW":
    case "SEPARATION":
    case "MINSEPARATION":
    case "MAXSEPARATION":
      this.warnExcessArguments(expr, args, 2);
      if (expr.getArguments().size() < 1) {
        String msg = "Required argument (size parameter) missing.";
        args.addError(expr, msg);
      } else {
        try {
          sizeParam = Integer.parseInt(expr.getArguments().get(0));
        } catch (NumberFormatException e) {
          String msg =
              "Required argument (size parameter) at position 0 must be an integer value.";
          args.addError(expr, msg);
        }
        if (sizeParam < 0) {
          String msg =
              "Required argument (size parameter) at position 0 must be greater than zero";
          args.addError(expr, msg);
        }
      }
      if (expr.getArguments().size() == 2) {
        runClassName = expr.getArguments().get(1);
      }
      break;
    default:
      // Do nothing
    }
    RunClass runClass = runClassName != null ? new RunClass(runClassName) : null;
    switch (expr.getName()) {
    case "AND":
      return new BooleanAnd(expr, resolvedSubExprs);
    case "OR":
      return new BooleanOr(expr, resolvedSubExprs);
    case "NOT":
      assert resolvedSubExprs.size() == 1;
      return new BooleanNot(expr, resolvedSubExprs.get(0));
    case "FIND":
      assert resolvedSubExprs.size() == 1;
      return new StructureFind(expr, resolvedSubExprs.get(0), runClass);
    case "IN":
      assert resolvedSubExprs.size() == 1;
      return new StructureIn(expr, resolvedSubExprs.get(0), runClass);
    case "SAME":
      assert resolvedSubExprs.size() == 1;
      return new StructureSame(expr, resolvedSubExprs.get(0), runClass);
    case "WINDOW":
      assert resolvedSubExprs.size() == 1;
      return new PositionWindow(expr, resolvedSubExprs.get(0), sizeParam, runClass);
    case "ORDERED":
      assert resolvedSubExprs.size() == 1;
      return new PositionOrdered(expr, resolvedSubExprs.get(0));
    case "PHRASE":
      assert resolvedSubExprs.size() == 1;
      return new PositionPhrase(expr, resolvedSubExprs.get(0));
    case "SEPARATION":
      assert resolvedSubExprs.size() == 1;
      return new PositionSeparation(expr, resolvedSubExprs.get(0), sizeParam,
                                    runClass);
    case "MINSEPARATION":
      assert resolvedSubExprs.size() == 1;
      return new PositionMinSeparation(expr, resolvedSubExprs.get(0), sizeParam,
                                       runClass);
    case "MAXSEPARATION":
      assert resolvedSubExprs.size() == 1;
      return new PositionMaxSeparation(expr, resolvedSubExprs.get(0), sizeParam,
                                       runClass);
    default:
      args.addError(expr, "Function name not recognied.");
      return null;
    }
  }

  @Override
  public Expression
      visitTerminalAbstractExpression(final TerminalSyntaxExpression expr,
                                      final ValidationTraversalLog args)
          throws VisitorException {
    return new KeywordMatchExpr(expr, expr.getToken(), expr.isReferenced(),
                                expr.getReference());
  }

  private void warnAtLeastOneInput(final List<Expression> resolvedSubExprs,
                                   final NonterminalSyntaxExpression expr,
                                   final ValidationTraversalLog arg) {
    if (resolvedSubExprs.size() < 1) {
      String msg =
          "Nonterminal expression has no sub-expressions. Expression will be pruned "
              + "from the expression tree.";
      arg.addError(expr, msg);
    }
  }

  private void warnExcessArguments(final NonterminalSyntaxExpression expr,
                                   final ValidationTraversalLog arg, final int i) {
    if (expr.getArguments().size() > i) {
      String msg =
          "Has more arguments than expected.  Expected %s found %s. "
              + "Unexpected arguments ignored.";
      arg.addWarning(expr, String.format(msg, i, expr.getArguments().size()));
    }
  }
}
